author = "Peter J Usherwood"
Esta tutorial é um exemplo de um aplicação em Python padrão, sem pacotes não padrão. Porque de isto, este codigo não é o mais simples ou eficiente, mas é transparente e um bom ferremento para apreder ambos Python, e cassificadores de apredizado de máquina. Isto é um aplicação útil tambem, vai dar os mesmos resultados de outros pactoes.
A aplicação nos vamos criar aqui é uma árvore de classificação. Uma árvore de classificação é um modelo de apredizado de maquinas classical que é usado para prever a classe de algumas observaçoes. este tecnico é usado hoje em muitas apliçoes comercial. É um modelo que aprende sobre lendo muitos exemplos de dados onde a classe é conhecido para aprender as regras que assina os arquivos para uma classe.
In [1]:
from random import seed
from random import randrange
import random
from csv import reader
from math import sqrt
import copy
In [2]:
# carregar o arquivo de CSV
def carregar_csv(nome_arquivo):
dados = list()
with open(nome_arquivo, 'r') as arquivo:
leitor_csv = reader(arquivo)
for linha in leitor_csv:
if not linha:
continue
dados.append(linha)
return dados
def str_coluna_para_int(dados, coluna):
classe = [row[column] for row in dataset]
unique = set(class_values)
lookup = dict()
for i, value in enumerate(unique):
lookup[value] = i
for row in dataset:
row[column] = lookup[row[column]]
return lookup
# Convert string column to float
def str_column_to_float(dataset, column):
column = column
dataset_copy = copy.deepcopy(dataset)
for row in dataset_copy:
row[column] = float(row[column].strip())
return dataset_copy
In [3]:
# carregar os dados
arquivo = '../../data_sets/sonar.all-data.csv'
dados = carregar_csv(arquivo)
# converte atributos de string para números inteiros
for i in range(0, len(dados[0])-1):
dados = str_column_to_float(dados, i)
dados_X = [linha[:-1] for linha in dados]
dados_Y = [linha[-1] for linha in dados]
Proximo nos precisamos fazer os dados tem a mesma quantidade de cada classe. Isso é importante por a maioria de classificadores em machine learning porque se não o classificador preveria o modo cada vez, porque isso daria a melhor precisão.
Tambem nos precisamos dividir os nossos dados dentro dois conjuntos: um conjunto de trem, e um conjunto de teste. Nos nao vamos olhar com o nosso conjunto de teste, vamos só usar esse no fim para dar uma precisão. O trem nos usaremos para treinar a nossa árvore. Normalmente vamos usar 80% dos nossos dados para a treinar, 20% para a provar.
Nos poderiamos fazer esses passos em o outro ordem, o resulto é mais ou menos a mesma.
In [4]:
def equilibrar_as_classes(dados_X, dados_Y):
classes = set(dados_Y)
conta_min = len(dados_Y)
for classe in classes:
conta = dados_Y.count(classe)
if conta < conta_min:
conta_min = conta
dados_igual_X = []
dados_igual_Y = []
indíces = set()
for classe in classes:
while len(dados_igual_Y) < len(classes)*conta_min:
indíce = random.randint(0,len(dados_X)-1)
classe = dados_Y[indíce]
if (indíce not in indíces) and (dados_igual_Y.count(classe) < conta_min):
indíces.update([indíce])
dados_igual_X.append(dados_X[indíce])
dados_igual_Y.append(dados_Y[indíce])
return dados_igual_X, dados_igual_Y
def criar_divisão_trem_teste(dados_X, dados_Y, relação=.8):
classes = set(dados_Y)
n_classes = len(classes)
trem_classe_tamanho = int((len(dados_Y)*relação)/n_classes)
indíces_todo = set(range(len(dados_X)))
indíces_para_escolher = set(range(len(dados_X)))
indíces = set()
trem_X = []
trem_Y = []
teste_X = []
teste_Y = []
while len(trem_Y) < trem_classe_tamanho*n_classes:
indíce = random.choice(list(indíces_para_escolher))
indíces_para_escolher.remove(indíce)
classe = dados_Y[indíce]
if (trem_Y.count(classe) < trem_classe_tamanho):
indíces.update([indíce])
trem_X.append(dados_X[indíce])
trem_Y.append(dados_Y[indíce])
indíces_teste = indíces_todo - indíces
for indíce in indíces_teste:
teste_X.append(dados_X[indíce])
teste_Y.append(dados_Y[indíce])
return trem_X, trem_Y, teste_X, teste_Y
In [5]:
dados_igual_X, dados_igual_Y = equilibrar_as_classes(dados_X, dados_Y)
trem_X, trem_Y, teste_X, teste_Y = criar_divisão_trem_teste(dados_igual_X, dados_igual_Y, relação=.8)
Proximo nos podemos começar construir a nossa árvore.
A árvore vai funcionar de dividido os registros de dados dentro grupos onde a distribução de classes são distinto, ela vai fazer isso muitas vezes ate uma boa previsão pode feitado.
Cada vez a árvore faz uma divisão é chamado um nó.
Para fazeras divisões de um nó a árvore recebe um valor de dado, por uma característica, e olhando o que vai acontecer para a distribução dos classes se a árvore dividido todos os registros sobre essa valore, por esta característica. Se os classes sera em groupos com a diferencia maior, isso e bom. Para escolher qual valor de qual característica para usar, a árvore iterar atraves cada característica em cada registro dos dados. Então ela compara todas as divisões e eschole a melhor. A medida de quanto separado sáo os classes, é o gini indíce.
Para começar nos vamos criar a função que faz a divião por um nó, vamos chamar obter_melhor_divisão.
In [6]:
def obter_melhor_divisão(dados_X, dados_Y, n_características=None):
"""
Obter a melhor divisão pelo dados
:param dados_X: Lista, o conjuncto de dados
:param dados_Y: Lista, os classes
:param n_características: Int, o numero de características para usar, quando você está usando a árvore sozinha fica
esta entrada em None
:return: dicionário, pela melhor divisáo, o indíce da característica, o valor para dividir, e os groupos de registors
resultandos da divisão
"""
classes = list(set(dados_Y)) #lista único de classes
b_indíce, b_valor, b_ponto, b_grupos = 999, 999, 999, None
"""
# Addicionar os classes (dados_Y) para os registros
for i in range(len(dados_X)):
dados_X[i].append(dados_Y[i])
dados = dados_X
"""
if n_características is None:
n_características = len(dados_X) - 1
# Faz uma lista de características únicos para usar
características = list()
while len(características) < n_características:
indíce = randrange(len(dados_X[0]))
if indíce not in características:
características.append(indíce)
for indíce in características:
for registro in dados_X:
grupos = tentar_divisão(indíce, registro[indíce], dados_X, dados_Y)
gini = gini_indíce(grupos, classes)
if gini < b_ponto:
b_indíce, b_valor, b_ponto, b_grupos = indíce, registro[indíce], gini, grupos
return {'indíce':b_indíce, 'valor':b_valor, 'grupos':b_grupos}
def tentar_divisão(indíce, valor, dados_X, dados_Y):
"""
Dividir o dados sobre uma característica e o valor da caracaterística dele
:param indíce: Int, o indíce da característica
:param valor: Float, o valor do indíce por um registro
:param dados_X: List, o conjuncto de dados
:param dados_Y: List, o conjuncto de classes
:return: esquerda, direitaç duas listas de registros dividou de o valor de característica
"""
esquerda_X, esquerda_Y, direita_X, direita_Y = [], [], [], []
for linha_ix in range(len(dados_X)):
if dados_X[linha_ix][indíce] < valor:
esquerda_X.append(dados_X[linha_ix])
esquerda_Y.append(dados_Y[linha_ix])
else:
direita_X.append(dados_X[linha_ix])
direita_Y.append(dados_Y[linha_ix])
return esquerda_X, esquerda_Y, direita_X, direita_Y
def gini_indíce(grupos, classes):
"""
Calcular o indíce-Gini pelo dados diversão
:param grupos: O grupo de registros
:param classes: O conjuncto de alvos
:return: gini, Float a pontuação de pureza
"""
grupos_X = grupos[0], grupos[2]
grupos_Y = grupos[1], grupos[3]
gini = 0.0
for valor_alvo in classes:
for grupo_ix in [0,1]:
tomanho = len(grupos_X[grupo_ix])
if tomanho == 0:
continue
proporção = grupos_Y[grupo_ix].count(classes) / float(tomanho)
gini += (proporção * (1.0 - proporção))
return gini
In [ ]:
Agora que nos podemos obter a melhor divisão uma vez, nos precisamos fazer isso muitas vezes, e volta a resposta da árvore
In [7]:
def to_terminal(grupo_Y):
"""
Voltar o valor alvo para uma grupo no fim de uma filial
:param grupo_Y: O conjuncto de classes em um lado de uma divisão
:return: valor_de_alvo, Int
"""
valor_de_alvo = max(set(grupo_Y), key=grupo_Y.count)
return valor_de_alvo
def dividir(nó_atual, profundidade_max, tamanho_min, n_características, depth):
"""
Recursivo, faz subdivisões por um nó ou faz um terminal
:param nó_atual: o nó estar analisado agora, vai mudar o root
:param max_profundidade: Int, o número máximo de iterações
"""
esquerda_X, esquerda_Y, direita_X, direita_Y = nó_atual['grupos']
del(nó_atual['grupos'])
# provar por um nó onde um dos lados tem todos os dados
if not esquerda_X or not direita_X:
nó_atual['esquerda'] = nó_atual['direita'] = to_terminal(esquerda_Y + direita_Y)
return
# provar por profundidade maximo
if depth >= profundidade_max:
nó_atual['esquerda'], nó_atual['direita'] = to_terminal(esquerda_Y), to_terminal(direita_Y)
return
# processar o lado esquerda
if len(esquerda_X) <= tamanho_min:
nó_atual['esquerda'] = to_terminal(esquerda_Y)
else:
nó_atual['esquerda'] = obter_melhor_divisão(esquerda_X, esquerda_Y, n_características)
dividir(nó_atual['esquerda'], max_depth, min_size, n_características, depth+1)
# processar o lado direita
if len(direita_X) <= tamanho_min:
nó_atual['direita'] = to_terminal(direita_Y)
else:
nó_atual['direita'] = obter_melhor_divisão(direita_X, direita_Y, n_características)
dividir(nó_atual['direita'], max_depth, min_size, n_características, depth+1)
Finalmente nos criamos uma função simples que nos vamos excecutar para criar a árvore.
In [8]:
def criar_árvore(trem_X, trem_Y, profundidade_max, tamanho_min, n_características):
"""
Criar árvore
:param:
"""
root = obter_melhor_divisão(trem_X, trem_Y, n_características)
dividir(root, profundidade_max, tamanho_min, n_características, 1)
return root
E carrega!
In [9]:
n_características = len(dados_X[0])-1
profundidade_max = 10
tamanho_min = 1
árvore = criar_árvore(trem_X, trem_Y, profundidade_max, tamanho_min, n_características)
Agora nos podemos usar a nossa árvore para prever a classe de dados.
In [10]:
def prever(nó, linha):
if linha[nó['indíce']] < nó['valor']:
if isinstance(nó['esquerda'], dict):
return prever(nó['esquerda'], linha)
else:
return nó['esquerda']
else:
if isinstance(nó['direita'], dict):
return prever(nó['direita'], linha)
else:
return nó['direita']
Agora nos podemos fazer preveções usando a nossa função prever, é melhor se nos usamos registros no nosso conjuncto de teste porque a árvore nao viu essas antes. Nos podemos fazer uma previção e depois comparar o resulto para a classe atual.
In [14]:
teste_ix = 9
print('A classe preveu da árvore é: ', str(prever(árvore, teste_X[teste_ix])))
print('A classe atual é: ', str(teste_Y[teste_ix][-1]))
Proximo nos vamos criar uma função que vai comparar tudos os registros no nosso conjunto de teste e da a precisão para nos. A precisão é definido de o por cento a árvore preveu corrigir.
In [12]:
def precisão(teste_X, teste_Y, árvore):
pontos = []
for teste_ix in range(len(teste_X)):
preverção = prever(árvore, teste_X[teste_ix])
if preverção == teste_Y[teste_ix]:
pontos += [1]
else:
pontos += [0]
precisão_valor = sum(pontos)/len(pontos)
return precisão_valor, pontos
In [13]:
precisão_valor = precisão(teste_X, teste_Y, árvore)[0]
precisão_valor
Out[13]: